package phaseutil

import (
	"context"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/pkg/errors"
	agentv1beta1 "gopkg.openfuyao.cn/bkeagent/api/v1beta1"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"sigs.k8s.io/controller-runtime/pkg/client"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/command"
	bkessh "gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/remote"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
)

// PingBKEAgent ping bkeagent(send ping command) to check if it's ready
func PingBKEAgent(ctx context.Context, c client.Client, scheme *runtime.Scheme, bkeCluster *bkev1beta1.BKECluster) (error, []string, []string) {
	pingNodes := bkenode.Nodes{}
	for _, node := range bkeCluster.Spec.ClusterConfig.Nodes {
		if bkeCluster.GetNodeStateFlag(node.IP, bkev1beta1.NodeAgentPushedFlag) {
			pingNodes = append(pingNodes, node)
		}
	}
	const nodePerTime = 5
	var allTime = pingNodes.Length() * nodePerTime
	pingCommand := command.Ping{
		BaseCommand: command.BaseCommand{
			Ctx:             ctx,
			Client:          c,
			Scheme:          scheme,
			NameSpace:       bkeCluster.Namespace,
			ClusterName:     bkeCluster.Name,
			OwnerObj:        bkeCluster,
			RemoveAfterWait: true,
			Unique:          false,
			WaitInterval:    1 * time.Second,
			WaitTimeout:     time.Duration(allTime) * time.Second,
		},
		Nodes: pingNodes,
	}

	if err := pingCommand.New(); err != nil {
		return errors.Errorf("create ping Command failed: %v", err), nil, nil
	}
	err, successNodes, failedNodes := pingCommand.Wait()
	GenerateBKEAgentStatus(successNodes, bkeCluster)

	stdOut := GetCommandStdOut(pingCommand.Command, "ping")
	// get hostName from stdOut if node.HostName == ""

	// remove already has hostname nodes
	for _, node := range bkeCluster.Spec.ClusterConfig.Nodes {
		if node.Hostname != "" {
			delete(stdOut, node.Hostname+"/"+node.IP)
		}
	}

	// get All Keys
	keys := []string{}
	for k, _ := range stdOut {
		keys = append(keys, k)
	}

	// get hostName from stdOut if node.HostName == ""
	for index, node := range bkeCluster.Spec.ClusterConfig.Nodes {
		// stdOut Key is nodeHostName with nodeHostIP
		for i, key := range keys {
			v := strings.Split(key, "/")
			if len(v) == 2 {
				nodeHostName := v[0]
				nodeHostIp := v[1]

				// 通过IP比对
				if nodeHostIp != "" && node.IP == nodeHostIp {
					if bkeCluster.Spec.ClusterConfig.Nodes[index].Hostname != "" {
						continue
					}
					bkeCluster.Spec.ClusterConfig.Nodes[index].Hostname = nodeHostName
					//remove this key from keys
					keys = append(keys[:i], keys[i+1:]...)
					break
				}
			}

		}
	}

	if err != nil && !errors.Is(err, wait.ErrWaitTimeout) {
		return err, successNodes, failedNodes
	}
	return nil, successNodes, failedNodes
}

// PushAgent push bkeagent to all Nodes,and it's the only SshClient connection
// todo 和EnsureBKEAgent逻辑一致
func PushAgent(hosts []bkessh.Host, localKubeConfig []byte, ntpServer string) []string {
	if ntpServer != "" {
		file, err := os.ReadFile("/bkeagent.service.tmpl")
		if err != nil {
			return nil
		}
		ntpServer = strings.ReplaceAll(string(file), "--ntpserver=", fmt.Sprintf("--ntpserver=%s", ntpServer))
		_ = os.WriteFile("/bkeagent.service", []byte(ntpServer), 0644)
	}

	startCommand := bkessh.Command{
		FileUp: []bkessh.File{
			{Src: "/bkeagent", Dst: "/usr/local/bin/"},
			{Src: "/bkeagent.service", Dst: "/usr/lib/systemd/system"},
		},
		Cmds: bkessh.Commands{
			"sudo chmod +x /usr/local/bin/bkeagent",
			// nodeName and localKubeConfig needs pre-exist before start bkeagent
			"sudo mkdir -p /etc/bkeagent",

			fmt.Sprintf("sudo echo -e %q > /etc/bkeagent/config", localKubeConfig),

			"sudo systemctl enable bkeagent",
			"sudo systemctl restart bkeagent",
		},
	}

	multiCli := bkessh.NewMultiCli(context.Background())
	defer multiCli.Close()

	var failedNodes []string

	regisErrs := multiCli.RegisterHosts(hosts)
	for hostIP, _ := range regisErrs {
		failedNodes = append(failedNodes, hostIP)
	}
	if len(failedNodes) == len(hosts) {
		return failedNodes
	}

	stdErrs, _ := multiCli.Run(startCommand)

	for nodeIP, _ := range stdErrs.Out() {
		failedNodes = append(failedNodes, nodeIP)
		//for i, err := range nodeErrs {
		//	if strings.Contains(err, "没有那个文件或目录") || strings.Contains(err, "No such file or directory") {
		//		utils.SliceRemoveString(nodeErrs, nodeErrs[i])
		//		nodeErrs = append(nodeErrs, "run bkeagent failed")
		//		break
		//	}
		//}
		//l.Errorf("push bkeagent to node %q failed: %v", no, nodeErrs)
	}
	return failedNodes

}

func GetCommandStdOut(cmd *agentv1beta1.Command, cmdId ...string) map[string]map[string][]string {
	stdOut := make(map[string]map[string][]string)
	if cmd == nil || len(cmd.Status) == 0 {
		return stdOut
	}
	for k, status := range cmd.Status {
		tmpStdOut := make(map[string][]string)
		for _, condition := range status.Conditions {
			if (cmdId != nil || len(cmdId) != 0) && utils.ContainsString(cmdId, condition.ID) {
				tmpStdOut[condition.ID] = condition.StdOut
				continue
			}
			if condition.StdOut != nil || len(condition.StdOut) != 0 {
				tmpStdOut[condition.ID] = condition.StdOut
			}
		}
		if len(tmpStdOut) != 0 {
			stdOut[k] = tmpStdOut
		}
	}
	return stdOut
}
